local version = {
  major = 0,
  minor = 0,
  revison = 6
}
local FSBuilder = {
  version = version
}
local MT = minetest
local is_mcl = MT.get_modpath("mcl_core") and 1 or 0

local modname = MT.get_current_modname()
local fs_esc = MT.formspec_escape
local theme = {
  bgcolor = "#000000BB",
  boxcolor = "#87CEFA17",
  lists = {
    normal = "#172d3a75",
    hover = "#1E90FF15",
    border = "#1E90FF40",
    tt_background = "#2F2F2F",
    tt_font = "#c1c1c1"
  },
  buttons = {
    normal = "#0E1A21",
    hover = "green",
    pressed = "#DAA520",
    alpha = "true"
  },
  color = "#c1c1c1"
}

local concat = function(data, separator)
  if data == nil then
    return ""
  else
    if type(data) == "string" then
      return data
    end
  end
  return table.concat(data, separator == nil and separator or "")
end

FSBuilder.theme = theme
FSBuilder.esc = fs_esc
FSBuilder.concat = concat
FSBuilder.is_mcl = is_mcl

function FSBuilder:new(def)
  def = def or {}
  local o = {
    name = "fsbuilder",
    valid = true,
    formspec = {},
    id = 0,
    has_container = false,
    height = def.height or 0,
    width = def.width or 0,
    spacing = 0.25,
    padding = 0.125,
    background = def.background or modname .. "_background",
    theme = def.theme
  }

  if def.auto_width == false then
    o.do_auto_width = false
  else
    o.do_auto_width = true
  end
  for key, value in pairs(theme) do
    if type(value) == "table" then
      for key2, value2 in pairs(value) do
        o.theme[key][key2] = o.theme[key][key2] or value[value2]
      end
    else
      o.theme[key] = o.theme[key] or value
    end
  end
  setmetatable(o, self)
  self.__index = self
  return o
end

function FSBuilder:set_height(height)
  self.height = tonumber(height) or 0
end

function FSBuilder:set_width(width)
  if not width or width < 1 then
    return
  end
  self.do_auto_width = false
  self.width = tonumber(width) or 0
end

function FSBuilder:auto_set_width()
  local width = self.is_mcl == 1 and 9 or 8
  self:set_width(self:slot(width) + self.padding)
end

function FSBuilder:set_background(background)
  self.background = background
end

function FSBuilder:set_size(height, width)
  self:set_height(height)
  self:set_width(width)
end

function FSBuilder:get_theme(key)
  return self.theme[key]
end

function FSBuilder:new_id()
  self.id = self.id + 1
  return self.id
end

function FSBuilder:add_element(str, ...)
  local id = self:new_id()
  self.formspec[id] = string.format(str, ...)
  return id
end

function FSBuilder:get_element(key)
  if self.formspec[key] ~= nil then
    return self.formspec[key]
  end
  return false
end

function FSBuilder:is_valid()
  return self.valid and true or false
end

function FSBuilder:set_invalid()
  self.valid = false
end

function FSBuilder:set_valid()
  self.valid = true
end

function FSBuilder:remove_element(key)
  if self.formspec[key] ~= nil then
    self.formspec[key] = nil
  end
end

function FSBuilder:get_formspec(add_header)
  if self.do_auto_width == true then
    self:auto_set_width()
  end
  local header
  if add_header == nil then
    header = self:add_header()
  elseif add_header == "minimum" then
    header = self:add_minimum_header()
  end
  local formspec_string = self.concat(self.formspec)

  return header .. formspec_string
end

function FSBuilder:add_minimum_header()
  return string.format([[
  formspec_version[4]
  size[%f,%f]
]], self.width, self.height)
end

function FSBuilder:add_header()
  return string.format([[
  formspec_version[4]
  size[%f,%f]
  background9[0,0;0,0;%s.png;true;4]
  bgcolor[%s;true]
  listcolors[%s;%s;%s;%s;%s]
  style_type[image_button,button,item_image_button;bgcolor=%s;bgcolor_hovered=%s;bgcolor_pressed=%s;alpha=%s,border=false,padding=1]
]], self.width, self.height, self.background, self.theme.bgcolor, self.theme.lists.normal, self.theme.lists.hover,
    self.theme.lists.border, self.theme.lists.tt_background, self.theme.lists.tt_font, self.theme.buttons.normal,
    self.theme.buttons.hover, self.theme.buttons.pressed, self.theme.buttons.alpha)
end

-- listring[<inventory location>;<list name>]
function FSBuilder:listring(location, list_name)
  self:add_element("listring[%s;%s]", location, list_name)
end

-- helper functions
function FSBuilder:slot(slot)
  slot = self.padding + slot
  return slot + (self.spacing * (slot + 1))
end

-- shortcut to slot
function FSBuilder:s(slot)
  return self:slot(slot)
end

-- MT formspec elements
-- ### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
function FSBuilder:list(loc, inv_name, x, y, w, h, start, x_offset, y_offset)
  self:add_element("list[%s;%s;%f,%f;%f,%f;%s]", fs_esc(loc), fs_esc(inv_name), x + (x_offset or 0),
    y + (y_offset or 0), w, h, start or "")
end

-- ### `label[<X>,<Y>;<label>]`
function FSBuilder:label(x, y, str)
  self:add_element("label[%f,%f;%s]", x, y, fs_esc(str))
end

-- ### `tooltip[<X>,<Y>;<W>,<H>;<tooltip_text>;<bgcolor>;<fontcolor>]`
function FSBuilder:tooltip(x, y, w, h, tooltip_text, bgcolor, fontcolor)
  tooltip_text = fs_esc((tooltip_text or ""))
  fontcolor = fs_esc((fontcolor and fontcolor or self:get_theme("lists").tt_font))
  bgcolor = fs_esc(bgcolor and bgcolor or self:get_theme("lists").tt_background)
  self:add_element("tooltip[%f,%f;%f,%f;%s;%s;%s]", x, y, w, h, tooltip_text, bgcolor, fontcolor)
end

-- ### `tooltip[<gui_element_name>;<tooltip_text>;<bgcolor>;<fontcolor>]`
function FSBuilder:tooltip_element(gui_element_name, tooltip_text, bgcolor, fontcolor)
  gui_element_name = fs_esc(gui_element_name)
  tooltip_text = fs_esc(tooltip_text)
  bgcolor = fs_esc(bgcolor and bgcolor or self:get_theme("lists").tt_background)
  fontcolor = fs_esc(fontcolor and fontcolor or self:get_theme("lists").tt_font)
  self:add_element("tooltip[%s;%s;%s;%s]", gui_element_name, tooltip_text, bgcolor, fontcolor)
end

-- ### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
function FSBuilder:item_image(x, y, w, h, item_name)
  self:add_element("item_image[%f,%f;%f,%f;%s]", x, y, w, h, fs_esc(item_name))
end

-- ### `button[<X>,<Y>;<W>,<H>;<name>;<label>]`
function FSBuilder:button(x, y, w, h, name, label)
  self:add_element("button[%f,%f;%f,%f;%s;%s]", x, y, w, h, fs_esc(name), fs_esc(label))
end

-- ### `image[<X>,<Y>;<W>,<H>;<texture name>]`
function FSBuilder:image(x, y, w, h, image)
  self:add_element("image[%f,%f;%f,%f;%s]", x, y, w, h, fs_esc(image))
end

-- ### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
-- ### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>;<pressed texture name>]`
function FSBuilder:image_button(x, y, w, h, image, name, label)
  self:add_element("image_button[%f,%f;%f,%f;%s;%s;%s]", x, y, w, h, fs_esc(image), fs_esc(name), fs_esc(label or ""))
end

-- ### `item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]`
function FSBuilder:item_image_button(x, y, w, h, item, name, label)
  self:add_element("item_image_button[%f,%f;%f,%f;%s;%s;%s]", x, y, w, h, fs_esc(item), fs_esc(name),
    fs_esc(label or ""))
end

-- box[<X>,<Y>;<W>,<H>;<color>]`
function FSBuilder:box(x, y, w, h, color)
  self:add_element("box[%f,%f;%f,%f;%s]", x, y, w, h, fs_esc(color or self:get_theme("boxcolor")))
end

-- field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]
function FSBuilder:field(x, y, w, h, name, label, default, query)
  query = query or default
  self:add_element("field[%f,%f;%f,%f;%s;%s;%s]" .. fs_esc(query), x, y, w, h, fs_esc(name or ""),
    fs_esc(label or ""), fs_esc(default or ""))
end

-- field_close_on_enter[<name>;<close_on_enter>]
function FSBuilder:field_close_on_enter(name, close_on_enter)
  close_on_enter = close_on_enter == false and "false" or "true"
  self:add_element("field_close_on_enter[%s;%s]", fs_esc(name), close_on_enter)
end

-- checkbox[<X>,<Y>;<name>;<label>;<selected>]
function FSBuilder:checkbox(x, y, name, label, selected)
  self:add_element("checkbox[%f,%f;%s;%s;%s]", x, y, fs_esc(name), fs_esc(label), selected and "true" or "false")
end

function FSBuilder:checkbox_tooltip(x, y, name, label, selected, tooltip_text)
  self:checkbox(x, y, name, label, selected)
  self:tooltip_element(name, tooltip_text)
end

-- helpers
function FSBuilder:get_trashcan(x, y)
  self:list("detached:vlib_trashcan", "main", self:slot(x), self:slot(y), 1, 1)
  self:tooltip(self:slot(x), self:slot(y), 1, 1, "Trash Can")
end

function FSBuilder:get_player_list(x, y)
  if is_mcl == 1 then
    -- inventory
    self:list("current_player", "main", self:slot(x), self:slot(y), 9, 1, 0, 0, 4)
    -- hotbar
    self:list("current_player", "main", self:slot(x), self:slot(y), 9, 3, 9)
  else
    -- hotbar
    self:list("current_player", "main", self:slot(x), self:slot(y), 8, 1)
    -- inventory
    self:list("current_player", "main", self:slot(x), self:slot(y), 8, 3, 8, 0, 1.5)
  end
end

function FSBuilder:get_player_crafting(x, y)
  x = x or 0
  y = y or 0
  self:list("current_player", "craft", self:slot(x), self:slot(y), 3, 3)
  self:list("current_player", "craftpreview", self:slot(x + 4), self:slot(y + 1), 1, 1)
  self:image(self:slot(x + 3), self:slot(y + 1), 1, 1, "vlib_arrow_bg.png^[transformR270]")
end

function FSBuilder:button_tooltip(x, y, w, h, name, label, tooltip_text)
  self:button(x, y, w, h, name, label)
  self:tooltip_element(name, tooltip_text)
end

-- ### `container[<X>,<Y>]`
function FSBuilder:container(x, y, content)
  self:add_element("container[%f,%f] %s container_end[]", x, y, fs_esc(content))
end

function FSBuilder:make_container(x, y)
  self.container = true
  self.x = x
  self.y = y
end

function FSBuilder:get_container()
  if not self.container then
    return false
  end
end

function FSBuilder:destroy()
  self = nil
end

return FSBuilder
